package com.baidu.controller;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.MD5;
import cn.hutool.http.HttpException;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.Method;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baidu.domain.InvoiceVerificationParameter;
import com.baidu.domain.InvoiceVerificationResult;
import com.baidu.util.Base64Util;
import com.baidu.util.FileUtil;
import com.baidu.util.HttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author liuzhiqiang
 */
@RestController
@CrossOrigin
@Slf4j
@RequestMapping("/baidu")
public class MultipleInvoiceController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private static final String appId = "appId";

    private static final String apiKey = "apiKey";

    private static final String secretKey = "secretKey";

    @PostMapping("/batchInvoiceVerification")
    public List<InvoiceVerificationResult> batchInvoiceVerification(@RequestBody List<InvoiceVerificationParameter> list) {
        List<InvoiceVerificationResult> result = new ArrayList<>();
        for (InvoiceVerificationParameter parameter : list) {
            InvoiceVerificationResult verificationResult = new InvoiceVerificationResult();
            verificationResult.setFileName(parameter.getFileName());
            verificationResult.setCode(200);
            try {
                JSONObject jsonObject = this.invoiceVerification(parameter.getFileName(), parameter.getUrl(), parameter.getRefId());
                verificationResult.setMetadata(jsonObject);
                verificationResult.setVerifyFrequency(jsonObject.getStr("VerifyFrequency"));
                verificationResult.setMachineCode(jsonObject.getStr("MachineCode"));
                verificationResult.setInvoiceNum(jsonObject.getStr("InvoiceNum"));
                verificationResult.setInvoiceDate(jsonObject.getStr("InvoiceDate"));
                verificationResult.setVerifyResult(jsonObject.getStr("VerifyResult"));
                verificationResult.setInvalidSign(jsonObject.getStr("InvalidSign"));
                verificationResult.setInvoiceCode(jsonObject.getStr("InvoiceCode"));
                verificationResult.setInvoiceType(jsonObject.getStr("InvoiceType"));
                verificationResult.setWordsResultNum(jsonObject.getStr("words_result_num"));
                verificationResult.setCheckCode(jsonObject.getStr("CheckCode"));
                verificationResult.setVerifyMessage(jsonObject.getStr("VerifyMessage"));
                verificationResult.setLogId(jsonObject.getStr("log_id"));
            } catch (Exception e) {
                verificationResult.setCode(500);
                verificationResult.setErrorMessage(e.getMessage());
            }
            result.add(verificationResult);
        }
        return result;
    }


    private JSONObject invoiceVerification(String name, String url, String refId) throws Exception {
        String refIdKey = "invoice_" + refId;
        String cacheResults = stringRedisTemplate.opsForValue().get(refIdKey);
        if (StrUtil.isNotBlank(cacheResults)) {
            return returnJsonObject(cacheResults);
        }
        String accessToken = accessToken();
        String type = FileUtil.getSuffix(name);
        byte[] fileByte = requestDownload(url, -1).bodyBytes();
        String str = Base64Util.encode(fileByte);
        String fileParam = URLEncoder.encode(str, "UTF-8");

        String param = "verify_parameter=true";
        String param2 = "";
        switch (type) {
            case "pdf":
                param2 = "pdf_file=" + fileParam;
                break;
            case "png":
            case "jpg":
            case "jpeg":
            case "bmp":
                param2 = "image=" + fileParam;
                break;
            default:
                throw new RuntimeException("文件格式不正确");
        }
        param = param + "&" + param2;
        String multipleInvoiceUrl = "https://aip.baidubce.com/rest/2.0/ocr/v1/multiple_invoice";
        String result = HttpUtil.post(multipleInvoiceUrl, accessToken, param);
        log.info(result);
        JSONObject jsonObject = JSONUtil.parseObj(result);
        if (ObjectUtil.isNotNull(jsonObject.get("error_code"))) {
            throw new RuntimeException(jsonObject.getStr("error_msg"));
        }

        JSONObject wordResult = jsonObject.getJSONArray("words_result").getJSONObject(0);


        // 发票验真
        String vatInvoiceVerificationUrl = "https://aip.baidubce.com/rest/2.0/ocr/v1/vat_invoice_verification";

        JSONObject verificationParameter = wordResult.getJSONObject("result");

        JSONArray invoiceCodeArr = verificationParameter.getJSONArray("invoice_code");
        String invoice_code = "";
        if (!invoiceCodeArr.isEmpty()) {
            invoice_code = invoiceCodeArr.getJSONObject(0).getStr("word");
        }

        JSONArray totalAmountArr = verificationParameter.getJSONArray("total_amount");
        String total_amount = "";
        if (!totalAmountArr.isEmpty()) {
            total_amount = totalAmountArr.getJSONObject(0).getStr("word");
        }

        JSONArray invoiceNumArr = verificationParameter.getJSONArray("invoice_num");
        String invoice_num = "";
        if (!invoiceNumArr.isEmpty()) {
            invoice_num = invoiceNumArr.getJSONObject(0).getStr("word");
        }


        JSONArray invoiceTypeArr = verificationParameter.getJSONArray("invoice_type");
        String invoice_type = "";
        if (!invoiceTypeArr.isEmpty()) {
            invoice_type = invoiceTypeArr.getJSONObject(0).getStr("word");
        }


        JSONArray checkCodeArr = verificationParameter.getJSONArray("check_code");
        String check_code = "";
        if (!checkCodeArr.isEmpty()) {
            check_code = checkCodeArr.getJSONObject(0).getStr("word");
        } else if (StrUtil.equals(invoice_type, "normal_invoice")) {
            // 增值税普通发票获取不到 校验码 在调用一次获取 数电票号码
            try {
                result = HttpUtil.post(multipleInvoiceUrl, accessToken, param2);
                log.info("result2: {}", result);
                check_code = JSONUtil.parseArray(JSONUtil.parseObj(result).getJSONArray("words_result").getJSONObject(0).getJSONObject("result").getStr("InvoiceNumDigit")).getJSONObject(0).getStr("word");
            } catch (Exception e) {
                log.error(e.getMessage());
                throw new RuntimeException("获取数电票号码失败");
            }
        }

        if (StrUtil.isNotBlank(check_code) && check_code.length() >= 6) {
            check_code = StrUtil.subSufByLength(check_code, 6);
        }


        JSONArray invoiceDateArr = verificationParameter.getJSONArray("invoice_date");
        String invoice_date = "";
        if (!invoiceDateArr.isEmpty()) {
            invoice_date = invoiceDateArr.getJSONObject(0).getStr("word");
        }

        String verificationParameterStr = "invoice_code=" + invoice_code + "&invoice_num=" + invoice_num + "&invoice_date=" + invoice_date + "&check_code=" + check_code + "&invoice_type=" + invoice_type + "&total_amount=" + total_amount;

        String yzKey = "invoice_" + MD5.create().digestHex(verificationParameterStr);

        String verificationResult = stringRedisTemplate.opsForValue().get(yzKey);
        if (StrUtil.isBlank(verificationResult)) {
            verificationResult = HttpUtil.post(vatInvoiceVerificationUrl, accessToken, verificationParameterStr);
            log.info(verificationResult);
            stringRedisTemplate.opsForValue().set(yzKey, verificationResult, 24, TimeUnit.HOURS);
        }
        stringRedisTemplate.opsForValue().set(refIdKey, verificationResult, 3600, TimeUnit.SECONDS);
        return returnJsonObject(verificationResult);
    }

    private JSONObject returnJsonObject(String verificationResult) {
        JSONObject jsonObject = JSONUtil.parseObj(verificationResult);
        if (ObjectUtil.isNotNull(jsonObject.get("error_code"))) {
            throw new RuntimeException(jsonObject.getStr("error_msg"));
        }
        return jsonObject;
    }


    private synchronized String accessToken() {
        RestTemplate restTemplate = new RestTemplate();
        String key = "baidu_accessToken";
        String accessToken = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(accessToken)) {
            return accessToken;
        }
        String url = "https://aip.baidubce.com/oauth/2.0/token?client_id=" + apiKey + "&client_secret=" + secretKey + "&grant_type=client_credentials";
        HttpHeaders requestHeaders = new HttpHeaders();
        requestHeaders.add("Content-Type", "application/json");
        requestHeaders.add("Accept", "application/json");
        Map<String, Object> body = new HashMap<>();
        body.put("id", 1);
        HttpEntity<Map> requestEntity = new HttpEntity<>(body, requestHeaders);
        ResponseEntity<String> entity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
        log.info(entity.getBody());
        JSONObject jsonObject = JSONUtil.parseObj(entity.getBody());
        if (jsonObject.get("error") != null) {
            throw new RuntimeException("获取accessToken失败");
        }
        accessToken = jsonObject.getStr("access_token");
        Integer expires_in = jsonObject.getInt("expires_in");
        stringRedisTemplate.opsForValue().set(key, accessToken, (expires_in - 1000), TimeUnit.SECONDS);
        return accessToken;
    }

    private static HttpResponse requestDownload(String url, int timeout) {
        Assert.notBlank(url, "[url] is blank !", new Object[0]);
        HttpResponse response = createGet(url, true).timeout(timeout).executeAsync();
        if (response.isOk()) {
            return response;
        } else {
            log.info("文件下载失败");
            throw new HttpException("Server response error with status code: [{}]", new Object[]{response.getStatus()});
        }
    }

    public static HttpRequest createGet(String url, boolean isFollowRedirects) {
        return get(url).setFollowRedirects(isFollowRedirects);
    }

    public static HttpRequest get(String url) {
        return (new HttpRequest(url)).method(Method.GET);
    }
}
